from enum import unique, Enum
from functools import partial
from typing import Tuple, Union, Callable, List
import traceback
from PyQt5.QtCore import Qt, QRect, QMargins, QSize, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QPushButton, QComboBox, QLineEdit, QFrame, \
    QAbstractItemView, QHeaderView, QSizePolicy, QStyledItemDelegate, QStyleOptionViewItem, QWidget, QVBoxLayout


@unique
class CustomListItemType(Enum):
    BUTTON = 1
    COMBOBOX = 2
    EDIT = 3
    LABEL = 4
    CUSTOMITEM = 10


class CustomCellDelegate(QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super(CustomCellDelegate, self).initStyleOption(option, index)

    def createEditor(self, parent, option, index):
        return super(CustomCellDelegate, self).createEditor(parent, option, index)

    def updateEditorGeometry(self, editor, option, index):
        marg = QMargins(0, 0, 0, 0)
        if isinstance(editor, QPushButton):
            text = editor.text()
            size = editor.fontMetrics().boundingRect(text).size()
            if editor.icon().isNull() is False:
                size = size + editor.iconSize()
            if len(text) > 0:
                size.setWidth(size.width()+20)
                size.setHeight(size.height() + 8)
            else:
                size.setWidth(size.width() + 6)
                size.setHeight(size.height() + 6)
            if size.width() < option.rect.size().width():
                marg.setLeft((option.rect.size().width()-size.width())//2)
                marg.setRight(marg.left())
            if size.height() < option.rect.size().height():
                marg.setTop((option.rect.size().height()-size.height())//2)
                marg.setBottom(marg.top())
            newRct = option.rect.marginsRemoved(marg)
            # print(f'{text}, old size:{editor.size()}, expect size:{size}, margins:({marg.left()},{marg.top()},{marg.right()},{marg.bottom()}), old Geometry:{option.rect}, center:{option.rect.center()}, new Geometry:{newRct}, center:{newRct.center()}')
            editor.setGeometry(newRct)
        elif isinstance(editor, QComboBox):
            itemCount = editor.count()
            size = QSize()
            for i in range(itemCount):
                text = editor.itemText(i)
                tmpSize = editor.fontMetrics().boundingRect(text).size()
                if tmpSize.width() > size.width():
                    size = tmpSize

            size.setWidth(size.width() + 30)
            size.setHeight(size.height() + 8)
            if size.width() < option.rect.size().width():
                marg.setLeft((option.rect.size().width() - size.width()) // 2)
                marg.setRight(marg.left())
            if size.height() < option.rect.size().height():
                marg.setTop((option.rect.size().height() - size.height()) // 2)
                marg.setBottom(marg.top())
            newRct = option.rect.marginsRemoved(marg)
            # print(f'{text}, old size:{editor.size()}, expect size:{size}, margins:({marg.left()},{marg.top()},{marg.right()},{marg.bottom()}), old Geometry:{option.rect}, center:{option.rect.center()}, new Geometry:{newRct}, center:{newRct.center()}')
            editor.setGeometry(newRct)
        else:
            editor.setGeometry(option.rect)

    def sizeHint(self, option, index):
        return super(CustomCellDelegate, self).sizeHint(option, index)

class CustomItemBase(QWidget):
    changed = pyqtSignal(str)
    _row = 0
    _column = 0

    def __init__(self, parent=None):
        super().__init__(parent)
        layout = self._onCreateSubWidgetAndLayout()
        if layout is not None:
            # layout = QVBoxLayout()
            layout.setContentsMargins(0, 0, 0, 0)
            self.setLayout(layout)

    def SetRowAndColumnIndex(self, rowIndex: int, columnIndex: int):
        self._row = rowIndex
        self._column = columnIndex

    def SetSubWidgetText(self, subWidget: int, value):
        self._onSetSubWidgetText(subWidget, value)

    def SwitchSubWidget(self, subWidget: int):
        self._onSwitch(subWidget)

    def _onCreateSubWidgetAndLayout(self):
        print('you should inherit: _onCreateSubWidgetAndLayout')
        return None

    def _onSetSubWidgetText(self, subWidget: int, value):
        print('you should inherit: _onSetSubWidgetText')

    def _onSwitch(self, subWidget: int):
        print('you should inherit: _onSwitch')


class CustomList(QTableWidget):
    __itemTypes = ()
    __btnCallbacks = {}
    __cmbCallbacks = {}
    __txtCallbacks = {}
    __edtMaxLen = {}
    __showGrid = False
    __showBorder = False

    def __init__(self, parent=None):
        super(CustomList, self).__init__(parent)
        self.__itemTypes = ()
        self.__btnCallbacks = {}
        self.__cmbCallbacks = {}
        self.__txtCallbacks = {}
        self.__edtMaxLen = {}
        self.setAlternatingRowColors(True)
        self.setShowGrid(self.__showGrid)  # 不显示网格线
        self.setFocusPolicy(Qt.NoFocus)  # 设置表格不接受焦点
        if self.__showBorder is False:
            self.setFrameShape(QFrame.NoFrame)  # 去除表格边框
        # self.setSelectionMode(QAbstractItemView.NoSelection)  # 设置为不可选择模式
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.verticalHeader().setVisible(False)
        self.horizontalHeader().setHighlightSections(False)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        #self.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.setMinimumHeight(100)

        delegate = CustomCellDelegate(self)
        self.setItemDelegate(delegate)

    def SetColumnTypes(self, types: Tuple, bSetColumnCount: bool = True):
        self.__itemTypes = types
        if bSetColumnCount is True:
            try:
                self.setColumnCount(len(types))
            except Exception as e:
                print(e)
        # for i, tp in enumerate(types):
        #     if tp == CustomListItemType.BUTTON:
        #         self.resizeColumnToContents(i)

    def SetColumnTitleAndTypes(self, titleAndTypes: Tuple):
        types = []
        titles = []
        for tt in titleAndTypes:
            titles.append(tt[0])
            types.append(tt[1])

        self.setColumnCount(len(types))
        self.setHorizontalHeaderLabels(titles)
        self.__itemTypes = types
        self.setMinimumWidth(60*len(types))
        # for i, tp in enumerate(types):
        #     if tp == CustomListItemType.BUTTON:
        #         self.resizeColumnToContents(i)

    # 设置一行数据
    def setRow(self, rowIndex: int, names: Tuple):
        if isinstance(names, Tuple) is False:
            return None
        rowCount = self.rowCount()
        columnCount = self.columnCount()
        if columnCount <= 0:
            return None
        if rowIndex >= rowCount:
            return None
        btnIndex = 1
        for i, name in enumerate(names):
            try:
                if self.__itemTypes[i] == CustomListItemType.BUTTON:
                    table_item = QPushButton()
                    # table_item.clicked.connect(lambda: self.__buttonClicked(rowIndex, i))
                    table_item.clicked.connect(partial(self.__buttonClicked, rowIndex, i))
                    self.setCellWidget(rowIndex, i, table_item)
                    if isinstance(name, str):
                        table_item.setText(name)
                        table_item.setObjectName(f'SubBtn_{btnIndex}')
                        btnIndex += 1
                    elif isinstance(name, dict):
                        if 'title' in name:
                            table_item.setText(name['title'])
                            table_item.setObjectName(f'SubBtn_{btnIndex}')
                            btnIndex += 1
                        if 'icon' in name:
                            table_item.setIcon(QIcon(name['icon']))
                        if 'bkg' in name:
                            fileName = name['bkg']
                            table_item.setStyleSheet(f'border-image: url({fileName});')
                    # table_item.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
                    # table_item.adjustSize()
                    # self.__resizeButton(table_item)

                elif self.__itemTypes[i] == CustomListItemType.COMBOBOX:
                    table_item = QComboBox()
                    self.setCellWidget(rowIndex, i, table_item)
                    table_item.addItems(name[0])
                    if isinstance(name[1], str):
                        table_item.setCurrentText(name[1])
                    elif isinstance(name[1], int):
                        table_item.setCurrentIndex(name[1])
                    # table_item.setAlignment(Qt.AlignCenter)
                    table_item.currentIndexChanged.connect(partial(self.__comboBoxChanged, rowIndex, i))

                elif self.__itemTypes[i] == CustomListItemType.EDIT:
                    table_item = QLineEdit(name)
                    table_item.setAlignment(Qt.AlignCenter)
                    if 0:
                        if i in self.__edtMaxLen:
                            table_item.setMaxLength(self.__edtMaxLen[i])
                    # table_item.textChanged.connect(partial(self.__textChanging, rowIndex, i))
                    table_item.editingFinished.connect(partial(self.__textChanged, rowIndex, i))
                    self.setCellWidget(rowIndex, i, table_item)

                elif self.__itemTypes[i] == CustomListItemType.CUSTOMITEM:
                    if isinstance(name, CustomItemBase):
                        self.setCellWidget(rowIndex, i, name)
                    else:
                        table_item = QTableWidgetItem('except CustomItemBase')
                        self.setItem(rowIndex, i, table_item)

                else:
                    table_item = QTableWidgetItem(name)
                    table_item.setTextAlignment(Qt.AlignCenter)
                    self.setItem(rowIndex, i, table_item)

            except Exception as e:
                print(f'{self.__class__.__name__}, except cell:{rowIndex}.{i}, {e}')
                if isinstance(name, str):
                    table_item = QTableWidgetItem(name)
                else:
                    table_item = QTableWidgetItem('except cell')
                self.setItem(rowIndex, i, table_item)


    def __buttonClicked(self, rowIndex: int, columnIndex: int):
        # print(f'click cell button:{rowIndex}.{columnIndex}')
        try:
            self.__btnCallbacks[columnIndex](rowIndex, columnIndex)
        except Exception as e:
            print(f'{str(e)} {traceback.format_exc()}')

    def __comboBoxChanged(self, rowIndex: int, columnIndex: int, index: int):
        # print(f"Index changed to: {index}, cell combo:{rowIndex}.{columnIndex}")
        try:
            self.__cmbCallbacks[columnIndex](rowIndex, columnIndex, index)
        except Exception as e:
            pass

    def __textChanging(self, rowIndex: int, columnIndex: int, msg):
        print(f"text changing to: {msg}, cell:{rowIndex}.{columnIndex}")

    def __textChanged(self, rowIndex: int, columnIndex: int):
        msg = self.sender().text()
        # print(f"text changed to: {msg}, cell:{rowIndex}.{columnIndex}")
        try:
            self.__txtCallbacks[columnIndex](rowIndex, columnIndex, msg)
        except Exception as e:
            pass

    def connectButton(self, columnIndex: int, callback: Callable[[int, int], None]):
        self.__btnCallbacks[columnIndex] = callback

    def connectComboBox(self, columnIndex: int, callback: Callable[[int, int, int], None]):
        self.__cmbCallbacks[columnIndex] = callback

    def connectLineEdit(self, columnIndex: int, callback: Callable[[int, int, str], None]):
        self.__txtCallbacks[columnIndex] = callback

    def __resizeButton(self, button):
        text = button.text()
        size = button.fontMetrics().boundingRect(text).size()
        if button.icon().isNull() is False:
            size = size + button.iconSize()
        #print(f'button:{text}, size:{size}')
        button.setFixedSize(size)

    def setCell(self, rowIndex: int, columnIndex: int, value):
        rowCount = self.rowCount()
        if rowIndex >= rowCount:
            return
        columnCount = self.columnCount()
        if columnIndex >= columnCount:
            return
        columnCount = len(self.__itemTypes)
        if columnIndex >= columnCount:
            return
        tp = self.__itemTypes[columnIndex]
        if tp == CustomListItemType.EDIT:
            if isinstance(value, str) is False:
                return
            edit = self.cellWidget(rowIndex, columnIndex)
            edit.setText(value)
        elif tp == CustomListItemType.COMBOBOX:
            cmb = self.cellWidget(rowIndex, columnIndex)
            if isinstance(value, int):
                cmb.setCurrentIndex(value)
            elif isinstance(value, str):
                cmb.setCurrentText(value)
            else:
                return
        elif tp == CustomListItemType.BUTTON:
            btn = self.cellWidget(rowIndex, columnIndex)
            if isinstance(value, str):
                btn.setText(value)
            elif isinstance(value, dict):
                if 'title' in value:
                    btn.setText(value['title'])
                if 'icon' in value:
                    btn.setIcon(QIcon(value['icon']))
                if 'bkg' in value:
                    fileName = value['bkg']
                    btn.setStyleSheet(f'border-image: url({fileName});')
        else:
            item = self.item(rowIndex, columnIndex)
            item.setText('')

    def setCellVisible(self, rowIndex: int, columnIndex: int, bVisible: bool):
        rowCount = self.rowCount()
        if rowIndex >= rowCount:
            return
        columnCount = self.columnCount()
        if columnIndex >= columnCount:
            return
        columnCount = len(self.__itemTypes)
        if columnIndex >= columnCount:
            return
        tp = self.__itemTypes[columnIndex]
        if tp != CustomListItemType.LABEL:
            item = self.cellWidget(rowIndex, columnIndex)
            if item is not None:
                item.setVisible(bVisible)

    # 清除某行的记录，根据单元格类型来清除
    def clearRow(self, rowIndex: int, types: List[CustomListItemType] = None):
        rowCount = self.rowCount()
        if rowIndex >= rowCount:
            return None
        for i, tp in enumerate(self.__itemTypes):
            needClear = False
            if types is None:
                needClear = True
            if tp in types:
                needClear = True
            if needClear is True:
                if tp == CustomListItemType.EDIT:
                    self.setCell(rowIndex, i, '')
                elif tp == CustomListItemType.COMBOBOX:
                    self.setCell(rowIndex, i, 0)
                elif tp == CustomListItemType.BUTTON:
                    self.setCell(rowIndex, i, '')
                else:
                    self.setCell(rowIndex, i, '')

    # 获取某行的数据 （columnIndexes可指定某些列）
    def getRow(self, rowIndex: int, columnIndexes: List[int] = None, withComboIndex: bool = True):
        rowCount = self.rowCount()
        if rowIndex >= rowCount:
            return None
        result = []
        for i, tp in enumerate(self.__itemTypes):
            needReturn = False
            if columnIndexes is None:
                needReturn = True
            else:
                if i in List:
                    needReturn = True
            if tp == CustomListItemType.COMBOBOX:
                cmb = self.cellWidget(rowIndex, i)
                if withComboIndex is True:
                    result.append(cmb.currentIndex())
                else:
                    result.append(cmb.currentText())
            elif tp == CustomListItemType.EDIT:
                edit = self.cellWidget(rowIndex, i)
                result.append(edit.text())
            elif tp == CustomListItemType.BUTTON:
                btn = self.cellWidget(rowIndex, i)
                result.append(btn.text())
            else:
                item = self.item(rowIndex, i)
                result.append(item.text())
        return result

    def setSelectRow(self, rowIndex: int):
        #self.setCurrentCell(rowIndex, 0)
        self.selectRow(rowIndex)

    # 设置列表中输入框子控件的最大长度
    def SetEditMaxLen(self, columnIndex: int, maxLen: int):
        self.__edtMaxLen[columnIndex] = maxLen



if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication
    import sys, os
    app = QApplication(sys.argv)
    current_path = os.path.abspath(__file__)
    current_path = os.path.dirname(current_path)
    current_path = os.path.dirname(current_path)
    try:
        with open(f'{current_path}\\styleSheet.qss', 'r', encoding='utf-8') as qssfile:
            content = qssfile.read()
            print('read qss file ok')
            app.setStyleSheet(content)
    except Exception as e:
        print('加载QSS失败')


    print('########################')
    wd = CustomList()
    wd.setGeometry(QRect(100, 100, 1300, 300))
    wd.SetColumnTypes((CustomListItemType.LABEL))
    #wd.SetColumnTypes((CustomListItemType.LABEL, CustomListItemType.COMBOBOX, CustomListItemType.BUTTON, CustomListItemType.EDIT, CustomListItemType.EDIT, CustomListItemType.BUTTON, CustomListItemType.BUTTON))
    #wd.SetColumnTitleAndTypes((('相机列表', CustomListItemType.LABEL), ('品牌', CustomListItemType.COMBOBOX), ('', CustomListItemType.BUTTON), ('IP', CustomListItemType.EDIT), ('备注', CustomListItemType.EDIT), (' ', CustomListItemType.BUTTON), (' ', CustomListItemType.BUTTON)))
    # wd.setColumnCount(7)

    wd.setRowCount(1)
    wd.setRow(0, ('相机1', 'asdf'))
    # wd.setRow(0, ('相机1', (('海康','大华', 'xx', '网络相机'), '海康'), '搜索', '192.168.1.1', '上相机', '设置', {'icon':f'{current_path}\\images\\reset.png'}))
    # wd.setRow(1, ('相机2', (('海康','大华', 'xx', '网络相机'), '大华'), '搜索', '', '', '设置', {'icon':f'{current_path}\\images\\reset.png'}))
    # wd.setRow(2, ('相机2', (('海康', '大华', 'xx', '网络相机'), '大华'), '搜索', '', '', '设置',
    #               {'icon': f'{current_path}\\images\\reset.png'}))
    # wd.setRow(3, ('相机2', (('海康', '大华', 'xx', '网络相机'), '网络相机'), '搜索', '', '', '设置',
    #               {'icon': f'{current_path}\\images\\reset.png'}))

    wd.show()
    sys.exit(app.exec_())